/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.opprua.tidy.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * TODO: add Java doc
 *
 * Prepared for first commit: 15.04.2007
 *
 * @author Lin Li
 */
public class IntCollection {

  private Collection<Integer> store = null;

  /**
   * Constructor for a Basket collection of integers.
   * 
   * @param distinct
   *          whether the collection will contain duplicated integers.
   */
  public IntCollection(boolean distinct) {
    if (distinct) {
      store = new TreeSet<Integer>();
    } else store = new ArrayList<Integer>();
  }

  /**
   * Constructor that allows duplicates in the collection.
   * 
   * @param o
   *          the initial values for this collection.
   */
  public IntCollection(Object o) {
    this(false);
    add(o);
  }

  /**
   * Constructor that allows duplicates in the collection.
   */
  public IntCollection() {
    this(false);
  }

  /**
   * Constructor for a Basket collection of integers.
   * 
   * @param distinct
   *          whether the collection will contain duplicated integers.
   * @param o
   *          the initial values for this collection.
   */
  public IntCollection(boolean distinct, Object o) {
    this(distinct);
    add(o);
  }

  /**
   * Add another (collection of) integer to the collection.
   * 
   * @param o
   *          additional values for this collection
   * @return the resulted collection
   */
  public IntCollection add(Object o) {
    collectionAction(true, store, o);
    return this;
  }

  /**
   * Add another int in the collection.
   * 
   * @param i
   *          one more value for the collection
   * @return the resulted collection
   */
  public IntCollection add(int i) {
    store.add(new Integer(i));
    return this;
  }

  /**
   * Remove an (collection of) integer from the collection. The integers that
   * were not in this collection have no effects.
   * 
   * @param o
   *          remove all values contained in this object from the collection
   * @return the resulting collection
   */
  public IntCollection remove(Object o) {
    collectionAction(false, store, o);
    return this;
  }

  /**
   * Remoev the integer from this collection. If the collection does not have
   * the int, nothing happens.
   * 
   * @param i
   *          remove one more value from the collection (all of them if there
   *          are duplicates)
   * @return the resulting collection
   */
  public IntCollection remove(int i) {
    while (store.remove(new Integer(i)))
      ;
    return this;
  }

  /**
   * Convert this collection into an array of ints.
   * 
   * @return convertion result.
   */
  public int[] toIntArray() {
    Integer[] data = (Integer[]) store.toArray(new Integer[store.size()]);
    int[] des = new int[data.length];
    for (int i = 0; i < des.length; i++) {
      des[i] = data[i].intValue();
    }
    return des;
  }

  /**
   * Print string as a ',' separated list of numbers. For large collections, the
   * string contain '\n' to form multiple lines.
   * 
   * @see java.lang.Object#toString()
   */
  public String toString() {
    StringBuffer buf = new StringBuffer();
    int i = 0;
    for (Iterator<Integer> iter = store.iterator(); iter.hasNext();) {
      Integer element = iter.next();
      if (i > 0) {
        buf.append(", ");
        if (i % 20 == 0) {
          buf.append("\n    ");
        }
      }
      buf.append(element.toString());
      ++i;
    }
    return buf.toString();
  }

  /**
   * Get the number of ints in the collection at the moment.
   * 
   * @return the size
   */
  public int size() {
    return store.size();
  }

  /**
   * Test if an (collection of) integer is in the collection. If the object is
   * itself a collection, every number in there must be in the collection in
   * order for this to return true.
   * 
   * @param o
   *          value for test
   * @return in the collection
   */
  public boolean contains(Object o) {
    int[] b = new IntCollection(true, o).toIntArray();
    for (int i = 0; i < b.length; i++) {
      if (!contains(b[i])) { return false; }
    }
    return true;
  }

  /**
   * Test if a integer is in the collection.
   * 
   * @param o
   *          value for test
   * @return in the collection
   */
  public boolean contains(int o) {
    return store.contains(new Integer(o));
  }

  /**
   * Converts the collection into a java.lang.Collection. The collection is
   * 'live' in the sense that anything happening to that collection happens to
   * the basket itself.
   * 
   * @return result of the convertion
   */
  public Collection<Integer> toCollection() {
    return store;
  }

  /**
   * Calculate the intersect of this collection with the other collection of
   * integers. Both collections will be made distinct first so that the
   * intersect is actually the value intersect instead of element intersect.
   * 
   * This methos does not change the collection itself but produces a new and
   * unrelated basket.
   * 
   * @param o
   *          the other collection
   * @return intersect of the this basket and the collection in o.
   */
  public IntCollection intersect(Object o) {
    IntCollection res = new IntCollection(true);
    IntCollection test = new IntCollection(true, o);
    int[] here = toIntArray();
    for (int i = 0; i < here.length; i++) {
      if (test.contains(here[i])) {
        res.add(here[i]);
      }
    }
    return res;
  }
  
  @SuppressWarnings("unchecked")
  private static void collectionAction(boolean add, Collection<?> col, Object o) {
    if (o == null || col == null) { return; }

    if (o.getClass().isArray()) {
      int length = Array.getLength(o);
      for (int i = 0; i < length; i++) {
        action(add, col, Array.get(o, i));
      }
    } else if (o instanceof Collection) {
      for (Iterator<Integer> iterator = ((Collection<Integer>) o).iterator(); iterator.hasNext();) {
        action(add, col, iterator.next());
      }
    } else if (o instanceof IntCollection) {
      for (Iterator<Integer> iterator = ((IntCollection) o).store.iterator(); iterator.hasNext();) {
        action(add, col, iterator.next());
      }
    } else {
      action(add, col, o);
    }
  }

  @SuppressWarnings("unchecked")
  private static void action(boolean add, Collection c, Object o) {
    Class<?> cl = o.getClass();
    Integer data = null;

    if (cl == Integer.class) {
      data = (Integer) o;
    } else if (cl == Long.class) {
      data = new Integer((int) ((Long) o).longValue());
    } else {
      throw new Error("Cannot convert type " + c + " into Integer");
    }

    if (add) {
      c.add((Integer) data);
    } else {
      while (c.remove(data))
        ;
    }
  }
}